Dans le cadre de la formation DSA, nous avons eu l’occasion d’analyser un jeu de données de tarification MRH à l’occasion d’un Hackathon.
Du fait des ontraintes propres au Hackathon je n’avais pas eu l’occasion de finaliser l’analyse de ce jeu de données. Par ailleurs, pour le hackathon, le choix s’était porté sur le logiciel Python.
Ce TD est donc l’occasion de poursuivre l’analyse de ce jeu de données et d’explorer l’usage de packages et d’approches développés par MV Wütrich et disponible sur le site de l’association suisse des actuaires.
Le code suivant est un ensemble d’utilitaire pour naviguer dans les répertoires relatifs du projet et installer ou charger les packages nécessaires à l’analyse :
if (!require("here")){
install.packages("here")
library("here")
}
set_here
LoadPackage <- function (load.lib=c("")) {
install.lib<-load.lib[!load.lib %in% installed.packages()]
for(lib in install.lib) install.packages(lib,dependencies=TRUE)
sapply(load.lib,require,character=TRUE)
}
LoadPackage(c('ggplot2', 'dplyr', 'formattable', 'DT', 'tidyr', 'caret', 'plotly'))
Le jeu de données est constitué de 4 fichiers :
list.files(path=here('data', 'raw'))
## [1] "Data" "Data.zip" "dsa2021_hackathon.zip"
## [4] "expo_test.csv" "expo_train.csv" "primes2019.csv"
## [7] "sin_train.csv"
Le projet suit un portefeuille d’assurance MRH suivi sur plusieurs années de 2016 à 2018 inclus. L’objectif initial du Hackathon est d’estimer les primes 2019 sur un échantillon de contrats.
Le fichier expo_train.csv contient la liste des contrats en risques historiquement suivis :
expo_train = read.table(file = here('data', 'raw', 'expo_train.csv'), header=T, sep=',', dec='.', encoding = 'UTF-8', stringsAsFactors = F)
datatable(head(expo_train))
str(expo_train)
## 'data.frame': 155651 obs. of 19 variables:
## $ X : int 384538 441079 119668 5124 130065 450830 151401 296526 71801 360445 ...
## $ EXPO : num 1 0.825 1 1 1 ...
## $ FORMULE : chr "MEDIUM" "CONFORT" "ESSENTIEL" "ESSENTIEL" ...
## $ TYPE_RESIDENCE : chr "PRINCIPALE" "PRINCIPALE" "PRINCIPALE" "SECONDAIRE" ...
## $ TYPE_HABITATION : chr "APPARTEMENT" "MAISON" "APPARTEMENT" "MAISON" ...
## $ NB_PIECES : int 1 NA 3 2 1 2 2 2 1 3 ...
## $ SITUATION_JURIDIQUE: chr "PROPRIO" "PROPRIO" "LOCATAIRE" "LOCATAIRE" ...
## $ NIVEAU_JURIDIQUE : chr "JUR1" "JUR1" "JUR1" "JUR1" ...
## $ VALEUR_DES_BIENS : num 3500 0 35000 9000 20000 9000 20000 9000 3500 0 ...
## $ OBJETS_DE_VALEUR : chr "NIVEAU_1" "NIVEAU_1" "NIVEAU_1" "NIVEAU_1" ...
## $ ZONIER : chr "B40" "A11" "B32" "C24" ...
## $ NBSIN_TYPE1_AN1 : int 0 0 0 0 0 0 1 0 0 0 ...
## $ NBSIN_TYPE1_AN2 : int 1 1 1 1 1 1 1 1 1 1 ...
## $ NBSIN_TYPE1_AN3 : int 0 0 0 0 0 0 0 0 0 0 ...
## $ NBSIN_TYPE2_AN1 : int 0 0 0 0 0 0 0 0 0 0 ...
## $ NBSIN_TYPE2_AN2 : int 0 0 0 0 0 0 0 0 0 0 ...
## $ NBSIN_TYPE2_AN3 : int 0 0 0 0 0 0 0 0 0 0 ...
## $ id : int 5 9 11 13 14 15 20 21 28 30 ...
## $ ANNEE : int 2017 2018 2017 2018 2017 2017 2018 2016 2018 2016 ...
Les données ont été prétraitées pour disposer d’une ligne par période de risque annuelle homogène.
Pour des raisons d’anonymisation, les identifiants contrats ont été supprimés de sorte qu’il n’est pas possible de suivre l’évolution du risque d’une période sur l’autre. Pour la même raison, il ne sera pas possible dans la construction des jeux de validation et de test de s’assurer que toute l’expérience d’un même assuré est bien intégralement dans un jeu d’entraînement, de validation ou de test.
La signification des colonnes présentes est la suivante :
X: variable non nommée. Il s’agit d’un artefact du processus de création du fichier initial. On ne la prend pas en compte
EXPO : exposition en année risque du contrat. Sa valeur précalculée pour chaque ligne est comprise entre 0 et 1
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.002732 0.811475 1.000000 0.837278 1.000000 1.000000
FORMULE : variable catégorielle codant la formule du contrat comprenant 3 niveaux MEDIUM, ESSENTIEL et CONFORT| FORMULE | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|
| ALL_INCLUDE | 6,671.00 | 4,759.51 | 3.65% |
| CONFORT | 87,603.00 | 73,930.57 | 56.73% |
| ESSENTIEL | 22,364.00 | 19,546.44 | 15.00% |
| MEDIUM | 39,013.00 | 32,086.59 | 24.62% |
TYPE_RESIDENCE : variable catégorielle codant le fait que le bien est une résidence PRINCIPALE, ou SECONDAIRE| TYPE_RESIDENCE | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|
| PRINCIPALE | 130,727.00 | 112,793.55 | 86.55% |
| SECONDAIRE | 24,924.00 | 17,529.57 | 13.45% |
TYPE_HABITATION : variable catégorielle codant le fait que le bien est un APPARTEMENT, ou une MAISON| TYPE_HABITATION | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|
| APPARTEMENT | 71,123.00 | 61,562.82 | 47.24% |
| MAISON | 84,528.00 | 68,760.30 | 52.76% |
NB_PIECES : variable numérique indiquant le nombre de pièces du logement| NB_PIECES | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|
| 0 | 3,718.00 | 2,413.72 | 1.85% |
| 1 | 40,300.00 | 31,943.51 | 24.51% |
| 2 | 60,973.00 | 51,931.99 | 39.85% |
| 3 | 31,695.00 | 27,730.87 | 21.28% |
| 4 | 9,615.00 | 8,495.12 | 6.52% |
| NA | 9,350.00 | 7,807.90 | 5.99% |
SITUATION_JURIDIQUE : variable catégorielle indiquant si le souscripteur est prorpiétaire (PROPRIO) ou locataire (LOCATAIRE) du logement assuré| SITUATION_JURIDIQUE | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|
| LOCATAIRE | 86,421.00 | 76,135.03 | 58.42% |
| PROPRIO | 69,230.00 | 54,188.09 | 41.58% |
NIVEAU_JURIDIQUE : variable catégorielle codant le niveau de couverture de la garantie juridique| NIVEAU_JURIDIQUE | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|
| JUR1 | 153,568.00 | 128,854.03 | 98.87% |
| JUR2 | 2,083.00 | 1,469.09 | 1.13% |
VALEUR_DES_BIENS : variable numérique reflétant la valeur couverte du contenu du logement| VALEUR_DES_BIENS | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|
| 0 | 8,307.00 | 5,259.05 | 4.04% |
| 3500 | 51,879.00 | 40,929.10 | 31.41% |
| 9000 | 34,770.00 | 30,059.53 | 23.07% |
| 20000 | 33,745.00 | 29,664.65 | 22.76% |
| 35000 | 11,528.00 | 10,299.00 | 7.90% |
| 50000 | 7,944.00 | 7,243.56 | 5.56% |
| 80000 | 6,534.00 | 6,018.46 | 4.62% |
| 100000 | 944.00 | 849.77 | 0.65% |
OBJETS_DE_VALEUR : variable catégorielle codant le niveau de couverture des objets| OBJETS_DE_VALEUR | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|
| NIVEAU_1 | 145,959.00 | 121,738.34 | 93.41% |
| NIVEAU_2 | 9,692.00 | 8,584.78 | 6.59% |
ZONIER : variable catégorielle codant la zone du bien assuré. Le code est constitué d’une lettre représentant une zone et d’un nombre représentant une partie de la zone| ZONIER | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|
| A1 | 1,822.00 | 1,579.15 | 1.21% |
| A10 | 1,125.00 | 963.04 | 0.74% |
| A11 | 4,775.00 | 4,140.40 | 3.18% |
| A12 | 3,115.00 | 2,694.44 | 2.07% |
| A13 | 1,879.00 | 1,593.61 | 1.22% |
| A14 | 828.00 | 695.12 | 0.53% |
| A2 | 1,750.00 | 1,466.96 | 1.13% |
| A3 | 1,129.00 | 975.75 | 0.75% |
| A4 | 81.00 | 62.13 | 0.05% |
| A5 | 16.00 | 14.27 | 0.01% |
| A6 | 698.00 | 610.63 | 0.47% |
| A7 | 256.00 | 222.42 | 0.17% |
| A8 | 66.00 | 61.27 | 0.05% |
| A9 | 4,082.00 | 3,526.65 | 2.71% |
| B1 | 793.00 | 701.39 | 0.54% |
| B10 | 3,520.00 | 3,005.74 | 2.31% |
| B11 | 11.00 | 8.94 | 0.01% |
| B12 | 342.00 | 289.71 | 0.22% |
| B13 | 44.00 | 37.32 | 0.03% |
| B14 | 116.00 | 98.55 | 0.08% |
| B16 | 211.00 | 185.81 | 0.14% |
| B17 | 3,530.00 | 2,992.31 | 2.30% |
| B18 | 8.00 | 6.25 | 0.00% |
| B19 | 950.00 | 800.99 | 0.61% |
| B2 | 438.00 | 385.83 | 0.30% |
| B20 | 6.00 | 5.58 | 0.00% |
| B21 | 44.00 | 37.82 | 0.03% |
| B22 | 59.00 | 52.15 | 0.04% |
| B23 | 214.00 | 183.13 | 0.14% |
| B24 | 135.00 | 118.09 | 0.09% |
| B25 | 641.00 | 551.08 | 0.42% |
| B26 | 1,477.00 | 1,264.44 | 0.97% |
| B27 | 188.00 | 167.93 | 0.13% |
| B28 | 268.00 | 228.86 | 0.18% |
| B29 | 4,269.00 | 3,645.34 | 2.80% |
| B3 | 1,956.00 | 1,691.09 | 1.30% |
| B30 | 506.00 | 443.22 | 0.34% |
| B31 | 79.00 | 71.24 | 0.05% |
| B32 | 7,015.00 | 5,906.43 | 4.53% |
| B33 | 186.00 | 167.03 | 0.13% |
| B34 | 708.00 | 616.63 | 0.47% |
| B35 | 3.00 | 2.28 | 0.00% |
| B36 | 454.00 | 377.39 | 0.29% |
| B37 | 150.00 | 126.09 | 0.10% |
| B38 | 85.00 | 70.05 | 0.05% |
| B39 | 2,543.00 | 2,159.86 | 1.66% |
| B4 | 1,868.00 | 1,603.70 | 1.23% |
| B40 | 5,776.00 | 4,859.50 | 3.73% |
| B41 | 402.00 | 341.69 | 0.26% |
| B42 | 29.00 | 25.69 | 0.02% |
| B43 | 6,038.00 | 5,066.02 | 3.89% |
| B44 | 6.00 | 5.09 | 0.00% |
| B45 | 452.00 | 379.98 | 0.29% |
| B5 | 34.00 | 30.47 | 0.02% |
| B6 | 122.00 | 105.72 | 0.08% |
| B7 | 353.00 | 303.13 | 0.23% |
| B8 | 239.00 | 214.63 | 0.16% |
| B9 | 608.00 | 519.74 | 0.40% |
| C1 | 2,760.00 | 2,325.40 | 1.78% |
| C10 | 899.00 | 723.85 | 0.56% |
| C11 | 1,322.00 | 1,067.75 | 0.82% |
| C12 | 2,453.00 | 1,917.28 | 1.47% |
| C13 | 1,236.00 | 959.28 | 0.74% |
| C14 | 1,372.00 | 1,095.67 | 0.84% |
| C15 | 1,202.00 | 947.23 | 0.73% |
| C16 | 1,272.00 | 998.16 | 0.77% |
| C17 | 845.00 | 656.75 | 0.50% |
| C18 | 739.00 | 605.29 | 0.46% |
| C19 | 2,897.00 | 2,317.49 | 1.78% |
| C2 | 6,067.00 | 5,172.98 | 3.97% |
| C20 | 14,581.00 | 12,217.25 | 9.37% |
| C21 | 4,816.00 | 4,002.97 | 3.07% |
| C22 | 2,555.00 | 2,050.02 | 1.57% |
| C23 | 6,214.00 | 5,119.00 | 3.93% |
| C24 | 1,972.00 | 1,621.60 | 1.24% |
| C3 | 3,041.00 | 2,571.18 | 1.97% |
| C4 | 1,713.00 | 1,444.03 | 1.11% |
| C5 | 6,833.00 | 5,665.22 | 4.35% |
| C6 | 5,176.00 | 4,382.99 | 3.36% |
| C7 | 2,103.00 | 1,736.06 | 1.33% |
| C8 | 5,550.00 | 4,455.87 | 3.42% |
| C9 | 9,535.00 | 7,810.05 | 5.99% |
NBSIN_TYPE1_AN1 : variable numérique indiquant le nombre de sinistre de type 1 l’année précédente| NBSIN_TYPE1_AN1 | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|
| 0 | 145,434.00 | 121,118.77 | 92.94% |
| 1 | 9,378.00 | 8,465.38 | 6.50% |
| 2 | 765.00 | 676.86 | 0.52% |
| 3 | 74.00 | 62.11 | 0.05% |
NBSIN_TYPE1_AN2 : variable numérique indiquant le nombre de sinistre de type 1 il y a 2 ansATTENTION : comme la table ci-dessous le démontre, cette variable présente un problème puisque toutes les lignes ont au moins un sinistre
Comme discuté dans le hackathon on ignore cette variable
| NBSIN_TYPE1_AN2 | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|
| 1 | 146,676.00 | 122,175.11 | 93.75% |
| 2 | 8,225.00 | 7,462.01 | 5.73% |
| 3 | 750.00 | 685.99 | 0.53% |
NBSIN_TYPE1_AN3 : variable numérique indiquant le nombre de sinistre de type 1 il y a 3 ans| NBSIN_TYPE1_AN3 | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|
| 0 | 147,569.00 | 122,889.56 | 94.30% |
| 1 | 7,403.00 | 6,807.03 | 5.22% |
| 2 | 625.00 | 576.62 | 0.44% |
| 3 | 54.00 | 49.91 | 0.04% |
NBSIN_TYPE2_AN1 : variable numérique indiquant le nombre de sinistre de type 2 l’année précédente| NBSIN_TYPE2_AN1 | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|
| 0 | 152,587.00 | 127,610.08 | 97.92% |
| 1 | 2,870.00 | 2,540.14 | 1.95% |
| 2 | 180.00 | 159.97 | 0.12% |
| 3 | 14.00 | 12.93 | 0.01% |
NBSIN_TYPE2_AN2 : variable numérique indiquant le nombre de sinistre de type 2 il y a 2 ans| NBSIN_TYPE2_AN2 | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|
| 0 | 136,330.00 | 114,011.30 | 87.48% |
| 1 | 2,058.00 | 1,861.63 | 1.43% |
| 2 | 113.00 | 103.72 | 0.08% |
| 3 | 8.00 | 8.00 | 0.01% |
| NA | 17,142.00 | 14,338.46 | 11.00% |
NBSIN_TYPE2_AN3 : variable numérique indiquant le nombre de sinistre de type 2 il y a 3 ans| NBSIN_TYPE2_AN3 | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|
| 0 | 153,501.00 | 128,360.07 | 98.49% |
| 1 | 2,019.00 | 1,844.33 | 1.42% |
| 2 | 125.00 | 113.36 | 0.09% |
| 3 | 6.00 | 5.37 | 0.00% |
id : identifiant du risque, clef de jointure
ANNEE : variable catégorielle indiquant l’année d’observation du risque : 2016, 2017, 2018
| ANNEE | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|
| 2016 | 54,283.00 | 45,171.85 | 34.66% |
| 2017 | 52,053.00 | 43,654.97 | 33.50% |
| 2018 | 49,315.00 | 41,496.30 | 31.84% |
Le fichier expo_train.csv contient la liste des contrats en risques historiquement suivis :
sin_train = read.table(file = here('data', 'raw', 'sin_train.csv'), header=T, sep=';', dec=',', encoding = 'UTF-8', stringsAsFactors = F)
datatable(head(sin_train))
str(sin_train)
## 'data.frame': 2693 obs. of 4 variables:
## $ id : int 15 277 643 668 730 1816 1852 2061 2270 2322 ...
## $ NB : int 1 1 1 1 1 1 1 1 1 1 ...
## $ COUT : num 521.4 3000.2 26.3 462.4 640.9 ...
## $ ANNEE: int 2017 2016 2018 2017 2018 2018 2016 2018 2018 2017 ...
id : identifiant du risque, clef de jointure avec le fichier expo_train
NB : Nombre de sinistres de la typologie modélisée (inconnue) sur la période d’observation
| NB | nb_lignes |
|---|---|
| 1 | 2,613.00 |
| 2 | 79.00 |
| 3 | 1.00 |
On peut désormais combine les information d’exposition et de sinistres :
mrh <- expo_train %>%
left_join(sin_train, by =c('id','ANNEE')) %>%
replace_na(list('NB'=0, 'COUT'=0))
N’ayant pas accèsau fichier de test utilisé lors du KAckathon, on dissocie ici le fichier mrh en un fichier train (70%), un fichier val (20%) et un fichier test (10%)
set.seed(42)
trainIndex<- createDataPartition(mrh$NB>=0, p=.7, list=FALSE, time=1)
mrh_train<-mrh[trainIndex,]
mrh_test<-mrh[ -trainIndex,]
valIndex<- createDataPartition(mrh_test$NB>=0, p=.66, list=FALSE, time=1)
mrh_val<-mrh_test[ valIndex,]
mrh_test<-mrh_test[ -valIndex,]
Taux de sinistres moyen :
sum(mrh_train$NB)/sum(mrh_train$EXPO)
## [1] 0.02116882
Taux de sinistre par variable :
## `summarise()` has grouped output by 'df[[feature]]'. You can override using the `.groups` argument.
## `summarise()` has grouped output by 'df[[feature]]'. You can override using the `.groups` argument.
plot_obs<-function(dt,feature) {
fig <- dt %>% plot_ly()
fig <- fig %>% add_trace(
x= dt[[feature]], y= dt[['EXPO']], type = 'bar', name = 'Exposition',
marker = list(color = '#C9EFF9'),
hoverinfo = "text",
text = ~paste(round(EXPO,0), ' années risque')
)
fig<- fig %>% add_trace(
x= dt[[feature]], y= dt[['Freq']], type = 'scatter', mode = 'lines', yaxis = 'y2', name= 'Fréquence',
hoverinfo = "text",
text = ~paste(round(Freq*100,2), '%')
)
fig <- fig %>% layout(title = paste0('Fréquence de sinistres par ', feature),
xaxis = list(title = ""),
yaxis = list(side = 'left', title = 'Exposition', showgrid = FALSE, zeroline = FALSE),
yaxis2 = list(side = 'right', overlaying = "y", title = 'Fréquence', showgrid = FALSE, zeroline = FALSE))
return(fig)
}
plot_obs<-function(dt,feature) {
fig <- dt %>% plot_ly()
fig <- fig %>% add_trace(
x= dt[[feature]], y= dt[['EXPO']], type = 'bar', name = 'Exposition',
marker = list(color = '#C9EFF9'),
hoverinfo = "text",
text = ~paste(round(EXPO,0), ' années risque')
)
fig<- fig %>% add_trace(
x= dt[[feature]], y= dt[['Freq']], type = 'scatter', mode = 'lines', yaxis = 'y2', name= 'Fréquence',
hoverinfo = "text",
text = ~paste(round(Freq*100,2), '%')
)
fig <- fig %>% layout(title = paste0('Fréquence de sinistres par ', feature),
xaxis = list(title = ""),
yaxis = list(side = 'left', title = 'Exposition', showgrid = FALSE, zeroline = FALSE),
yaxis2 = list(side = 'right', overlaying = "y", title = 'Fréquence', showgrid = FALSE, zeroline = FALSE))
return(fig)
}
resume <- function(df, feature) {
expo_tot <- sum(df$EXPO)
dt <-df %>%
group_by(df[[feature]]) %>%
summarise(Freq = sum(NB*EXPO)/sum(EXPO), nb_lignes = n(), EXPO=sum(EXPO) , pct_EXPO = sum(EXPO) / expo_tot )
colnames(dt)[1] <- feature
table<- formattable(dt,
align=c('l','r', 'r','r', 'r'),
list(
nb_lignes= accounting,
EXPO = accounting,
pct_EXPO = percent
)
)
fig<- plot_obs(dt, feature)
return(list(table=table, fig=fig))
}
| FORMULE | Freq | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|---|
| ALL_INCLUDE | 0.00572466 | 4,695.00 | 3,355.67 | 3.67% |
| CONFORT | 0.02106887 | 61,372.00 | 51,858.85 | 56.79% |
| ESSENTIEL | 0.02215368 | 15,555.00 | 13,597.27 | 14.89% |
| MEDIUM | 0.01689978 | 27,334.00 | 22,501.75 | 24.64% |
| VALEUR_DES_BIENS | Freq | nb_lignes | EXPO | pct_EXPO |
|---|---|---|---|---|
| 0 | 0.002537415 | 5,782.00 | 3,682.96 | 4.03% |
| 3500 | 0.013571577 | 36,543.00 | 28,888.32 | 31.64% |
| 9000 | 0.019541361 | 24,283.00 | 21,014.51 | 23.01% |
| 20000 | 0.022504026 | 23,639.00 | 20,768.83 | 22.74% |
| 35000 | 0.028644528 | 8,047.00 | 7,187.60 | 7.87% |
| 50000 | 0.031410312 | 5,470.00 | 4,991.12 | 5.47% |
| 80000 | 0.030885158 | 4,557.00 | 4,210.03 | 4.61% |
| 100000 | 0.037175500 | 635.00 | 570.18 | 0.62% |